﻿Imports System.Reflection
Imports System.Runtime.CompilerServices

Public Module JsonEncoder

    Public Function ToJson(ByVal O As Object) As String
        Dim ListProperties = O.GetType.GetProperties
        Dim SBuilder As New System.Text.StringBuilder
        Dim JPattern = """{0}"":{1},"
        For Each P In ListProperties
            If P.CanRead AndAlso IsJsonized(P) Then
                SBuilder.Append(String.Format(JPattern, camelCase(P.Name), GetPropertyValue(O, P)))
            End If
        Next
        Return String.Format("{{{0}}}", SBuilder.ToString.Trim.TrimEnd(","))
    End Function

    Private Function IsJsonized(ByVal P As PropertyInfo) As Boolean
        Dim Atts = P.GetCustomAttributes(False)
        If Atts.Count = 0 Then
            Return True
        End If
        For Each At In Atts
            If At.GetType.FullName.Contains("NonJsonizedAttribute") Then
                Return False
            End If
        Next
        Return True
    End Function

    Private Function GetPropertyValue(ByVal O As Object, ByVal P As PropertyInfo) As String
        If P.GetValue(O, Nothing) Is Nothing Then Return """"""

        Dim PType = P.PropertyType.FullName.ToLower.Replace("system.", "")

        If PType.Contains("list") Then
            Dim SBuilder As New System.Text.StringBuilder
            Dim ListO = DirectCast(P.GetValue(O, Nothing), IList)
            For Each L In ListO
                SBuilder.Append(String.Format("{0},", ToJson(L)))
            Next
            Return String.Format("[{0}]", SBuilder.ToString.Trim.TrimEnd(","))
        End If

        Dim JQuotedPattern = """{0}"""

        Select Case PType
            Case "string"
                Return String.Format(JQuotedPattern, EscapeJson(P.GetValue(O, Nothing).ToString))
            Case "int32", "int64"
                Return P.GetValue(O, Nothing).ToString()
            Case "datetime"
                Dim D = CDate(P.GetValue(O, Nothing))
                Return String.Format("""{0}-{1}-{2}T{3}:{4}:{5}""", D.Year, D.Month, D.Day, D.Hour, D.Minute, D.Second)
            Case "boolean"
                Return CBool(P.GetValue(O, Nothing)).ToString.ToLower
            Case "timespan"
                Return String.Format("""{0}""", TimeSpanFormat(DirectCast(P.GetValue(O, Nothing), TimeSpan)))
        End Select
        Return ""
    End Function

    Private Function EscapeJson(ByVal Json As String) As String
        Dim Boundary = "|@|{{{0}}}|@|"
        Boundary = String.Format(Boundary, Now.Millisecond)
        Json = Json.Replace("\n", Boundary)
        Json = Json.Replace("\", "\\").Replace("""", "\""").Replace("/", "\/")
        Json = Json.Replace(Boundary, "\n")
        Return Json
    End Function

    Private Function camelCase(ByVal Name As String) As String
        Return Name.Substring(0, 1).ToLower & Name.Substring(1)
    End Function

    Public Function TimeSpanFormat(ByVal TS As TimeSpan) As String
        Dim M = Math.Floor(TS.TotalMinutes).ToString("00")
        Dim S = (TS.TotalSeconds - CInt(M) * 60).ToString("00")
        Return String.Format("{0}:{1}", M, S)
    End Function

End Module
